﻿using LiteBlog.Common.Components;
using LiteBlog.Common.Entities;
using LiteBlog.Components.Contracts;
using LiteBlog.Components.Enums;
using LiteBlog.Components.Helpers;
using LiteBlog.Components.SiteMap;
using LiteBlog.Repositories.Contracts;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;

namespace LiteBlog.Components
{
    public class SiteMapComp : ISiteMapComp
    {
        private static readonly string SiteMapFileName = "sitemap-{0}.xml";
        private static readonly string DateTimeFormat = "yyyy-MM-dd";
        private static readonly string SiteMapXml = @"<?xml version=""1.0"" encoding=""UTF-8""?><urlset xmlns=""http://www.sitemaps.org/schemas/sitemap/0.9""></urlset>";
        private static readonly string SiteMapNamespace = "http://www.sitemaps.org/schemas/sitemap/0.9";

        private XName urlName;
        private XName locName;
        private XName freqName;
        private XName modName;
        private XName priorityName;

        public SiteMapComp()
        {
            urlName = XName.Get("url", SiteMapNamespace);
            locName = XName.Get("loc", SiteMapNamespace);
            modName = XName.Get("lastmod", SiteMapNamespace);
            freqName = XName.Get("changefreq", SiteMapNamespace);
            priorityName = XName.Get("priority", SiteMapNamespace);
        }

        private ICategoryRepository _categoryRepo;
        public ICategoryRepository CategoryRepo
        {
            get
            {
                return _categoryRepo;
            }
        }

        private IBlogRepository _blogRepo;
        public IBlogRepository BlogRepo
        {
            get
            {
                return _blogRepo;
            }
        }

        private IPageRepository _pageRepo;
        public IPageRepository PageRepo
        {
            get
            {
                return _pageRepo;
            }
        }

        public SiteMapComp(ICategoryRepository categoryRepo, IBlogRepository blogRepo, IPageRepository pageRepo)
        {
            _categoryRepo = categoryRepo;
            _blogRepo = blogRepo;
            _pageRepo = pageRepo;
        }

        public bool Generate(SiteMapType type)
        {
            // regeneration -- delete existing sitemap
            var filePath = GetSiteMapPath(type);
            if (File.Exists(filePath))
                File.Delete(filePath);

            var provider = SiteMapProviderFactory.GetProvider(this, type);
            var items = provider.GetSiteMapItems();

            bool result = true;
            foreach (var item in items)
                result = result && Add(type, item);

            return result;
        }

        public bool Add(SiteMapType type, SiteMapUrl url)
        {
            var document = GetSiteMap(type);
            var encodedLocation = GetEncodedLocation(url.Location);
            var element = document.Root.Elements(urlName).SingleOrDefault(elem => (string)elem.Element(locName) == encodedLocation);
            bool result = element == null;
            if (result)
            {
                var frequency = "daily";
                frequency = ((type == SiteMapType.Page) || (type == SiteMapType.Post)) ? "monthly" : "daily";
                element = new XElement(urlName,
                               new XElement(locName, encodedLocation),
                               new XElement(modName, url.LastModifiedTime.ToString(DateTimeFormat, null)),
                               new XElement(freqName, frequency),
                               new XElement(priorityName, url.Priority.ToString()));

                document.Root.AddFirst(element);
                var filePath = GetSiteMapPath(type);
                XmlHelper.Save(document, filePath);
            }
            return result;
        }

        public bool Update(SiteMapType type, SiteMapUrl url)
        {
            var document = GetSiteMap(type);
            var element = document.Root.Elements(urlName).SingleOrDefault(elem => (string)elem.Element(locName) == GetEncodedLocation(url.Location));
            if (element != null)
            {
                element.Element(modName).Value = url.LastModifiedTime.ToString(DateTimeFormat, null);
                element.Element(priorityName).Value = url.Priority.ToString();
                var filePath = GetSiteMapPath(type);
                XmlHelper.Save(document, filePath);
            }
            return element != null;
        }

        public bool UpdateUrl(SiteMapType type, string oldUrl, string newUrl)
        {
            var document = GetSiteMap(type);
            var element = document.Root.Elements(urlName).SingleOrDefault(elem => (string)elem.Element(locName) == GetEncodedLocation(oldUrl));
            if (element != null)
            {
                element.Element(modName).Value = DateTime.UtcNow.ToString(DateTimeFormat, null);
                element.Element(locName).Value = GetEncodedLocation(newUrl);
                var filePath = GetSiteMapPath(type);
                XmlHelper.Save(document, filePath);
            }
            return element != null;
        }

        public bool Delete(SiteMapType type, string location)
        {
            var document = GetSiteMap(type);
            var element = document.Root.Elements(urlName).SingleOrDefault(elem => (string)elem.Element(locName) == GetEncodedLocation(location));
            if (element != null)
            {
                element.Remove();
                var filePath = GetSiteMapPath(type);
                XmlHelper.Save(document, filePath);
            }
            return element != null;
        }

        private XDocument GetSiteMap(SiteMapType type)
        {
            var filePath = GetSiteMapPath(type);
            return XDocument.Load(filePath);
        }

        private string GetSiteMapPath(SiteMapType type)
        {
            var fileName = string.Format(SiteMapFileName, type.ToString().ToLower());
            var filePath = Path.Combine(ConfigComp.AppPath, fileName);
            if (!File.Exists(filePath))
            {
                var document = XDocument.Parse(SiteMapXml);
                XmlHelper.Save(document, filePath);
            }
            return filePath;
        }

        private string GetEncodedLocation(string location)
        {
            // Ideally location should be encoded. 
            // ümlat should be encoded to %FCmlat
            // Ampersand, Single Quote, Double Quote, Greater than, Lesser than, should be encoded
            // Querystrings should be encoded
            // Since LiteBlog does not use any of the above, I am not encoding location
            return ConfigComp.AppUrl + location;
        }
    }
}
